package com.wmx.hb.controller;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * [bas_flow_config] 配置控制层
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/12/16 9:41
 */
@RestController
public class FlowConfigController {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private FlowConfigNewTrans flowConfigNewTrans;

    /**
     * 注入 RedisTemplate 或者 StringRedisTemplate 其中一个即可，前者是后者的父类，它们都已经默认在 Spring 容器中了。
     * org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 中已经自动将 RedisTemplate 添加到了容器中，直接获取使用即可.
     */
    @Resource
    private RedisTemplate redisTemplate;

    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    /**
     * 并发压测测试接口———亲测每秒 500 并发完全没有问题
     * http://localhost:8080/flow/initFlowConfig
     * <p>
     * 为某个区划初始化数据，一个区划下有多个单位，每个单位的用户操作时，它们的区划只能初始化一条。
     * 如果不加处理，高并发下就会出现初始化多条的情况。
     * 1、初始化前先查询，如果已经存在，则直接返回，无需再初始化
     * 2、第一个初始化时进行加锁(往 redis 中插入一个key)，初始化完成后删除 key
     * 3、当出现并发时，其余的如果发现已经存在这个 key，则延迟等待1，然后再次查询是否已经存在，最多循环10次。
     * 4、初始化操作单独新开一个事务，完成后，其余线程立马就能读取到数据，这样就不需要等待整个线程事务提交，
     *
     * @return
     */
    @GetMapping("flow/initFlowConfig")
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> initFlowConfig(HttpServletRequest request) throws InterruptedException {
        Map<String, Object> resultMap = getResultMap(request);

        String querySql = "select * from bas_flow_config where mof_div_code='430000000'";
        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(querySql);

        if (mapList.isEmpty()) {
            Map<String, Object> initFlow = flowConfigNewTrans.initFlow("430000000");
            resultMap.put("data", initFlow);
        } else {
            resultMap.put("data", mapList.get(0));
        }
        System.out.println(request.getSession().getId());
        return resultMap;
    }

    /**
     * 并发压测测试接口———亲测每秒 500 并发完全没有问题
     * http://localhost:8080/redis/setIfAbsent
     * <p>
     * Boolean setIfAbsent(K var1, V var2)：如果 key 不存在，则新建，返回 true；如果已经存在，则不处理，直接返回 false。
     * 1、很多时候，比如分布式锁，就使用 setIfAbsent
     * 2、经过反复测试，每秒500并发，也只会有一个返回 true，即成功设置了锁。
     * 3、不推荐先使用 redisTemplate.hasKey("hasKey")判断key是否存在，然后不存在时，则进行 set，亲测在 500 并发时，会存在错误的情况，
     * 特别是 redis 中数据越多，出错的概率就越大。
     *
     * @param request
     * @return
     * @throws InterruptedException
     */
    @GetMapping("redis/setIfAbsent")
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> setIfAbsent(HttpServletRequest request) throws InterruptedException {
        Map<String, Object> resultMap = getResultMap(request);
        String id = request.getSession().getId();
        //模拟业务操作耗时.
        TimeUnit.MILLISECONDS.sleep(200 + new SecureRandom().nextInt(900));
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("setIfAbsent", id, 60, TimeUnit.SECONDS);
        //模拟业务操作耗时.
        TimeUnit.MILLISECONDS.sleep(200 + new SecureRandom().nextInt(900));
        System.out.println(atomicInteger.getAndAdd(1) + " ====" + id + "———— > " + ifAbsent);
        return resultMap;
    }

    private Map<String, Object> getResultMap(HttpServletRequest request) {
        Map<String, Object> resultMap = new HashMap<>(8);
        resultMap.put("code", "200");
        resultMap.put("msg", "success");
        resultMap.put("sessionId", request.getSession().getId());
        resultMap.put("data", null);
        return resultMap;
    }
}
